/*!
//      \file	k2k_main.c
//      
//      \author	Copyright 2010 Valentina Vuksic <vuksv1@bfh.ch>, Beat Liechti <liecb2@bfh.ch>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.
//		
//		\brief		CAS-EBX-03 Projektarbeit 
//					Valentina Vuksic (VUKSV1), Beat Liechti (LIECB2)
//					main for the k2k (Kaffee2000) application
//		\version	1.0
//		\date		2010-04-06
// 		\remark		Last Modifications:
//		\remark  	V1.0, liecb2, 2010-04-06   Initial release
//
*/

/*! \mainpage k2k Index Page
//
// \section EINLEITUNG
//
// Der Titel der Software für eine Kaffeemaschine lautet "k2 kaffee", das verwendete "k2" ist ein 
// Wortspiel von "2000". Im Code und der Dokumentation wird das Kürzel "k2k" verwendet. 
// \subsection Schalter
// Die Bezeichnung der Schalter basiert auf der Schaltung des Carme Boards: S0 – S7
// Bei X86 werden die Eingaben über das Terminal abgesetzt: Tasten 0 bis 7
// Die Schalter funktionieren wie Taster, d.h. Wechsel der Schalterstellung = ein Impuls = ein Event
// \subsection Audio
// Die Bedienung der Kaffeemaschine wird akustisch begleitet. Vorraussetzung für das Abspielen der 
// Audiodateien ist die Installation des "mplayer" und dessen Erreichbarkeit über den Pfad.
//
// \section FUNKTIONALITÄT
// Die Software ergänzt den in der Aufgabenstellung geforderten Umfang um folgende Funktionalität:
//
// \subsection Reinigungsmodus
// Die Kaffeemaschine wird automatisch gereinigt, nachdem mindestens fünf Getränke zubereitet wurden. 
// Die Reinigung kann direkt über S6 aktiviert werden.
//
// \subsection Akustische Ausgaben
//  Bei folgenden Aktionen wird ein Audio Clip mit mplayer abgespielt:
//  - Einschalten der Kaffeemaschine
//  - Änderung der vorgewählten Anzahl an Getränken
//  - Zubereitung eines Getränk (Espresso, Ristretto und Milchkaffee werden unterschieden)
//  - Reinigung
//  - Verlassen der Applikation
//
// \subsection DESIGN
// Die Kaffeemaschine ist als endlicher Automat implementiert. Das State Event Diagramm sieht wie folgt 
// aus: 
// \image html k2k_StateMachine_NoText_small.png
// Wird der Schalter S7 betätigt, beendet sich die Applikation unabhängig vom aktuellen Zustand. 
// Dies ist mit dem Ziehen des Netzsteckers vergleichbar. Daher wird S7 nicht als Event behandelt und ist 
// nicht auf dem Diagramm wiedergeben.
// 
// \subsection IMPLEMENTATION
// Abstract Data Types (ADT) kapseln zusammenhängende Datenstrukturen und Funktionen in je einer 
// Header­ und einer zugehörigen C­Datei. Sie erlauben einen modularen Aufbau und Quasi­
// Objektorientierung unter C. Die Namen der ADTs der Kaffeemaschinen­Software setzen sich aus dem 
// Prefix "k2k_", gefolgt von der Bezeichnung des ADTs zusammen, z.B. "k2k_StateMachine". 
// 
// Die wesentlichen Module:
// - k2k_main.c: Hauptprogramm (Ist kein ADT)
// - k2k_IO.h: Abstrahiert die Hardware (ADT)
// - k2k_X86IO.h: Abstrahiert X86 (ADT)
// - k2k_NanoX.h: Abstrahiert NanoX (ADT)
// - k2k_StateMachineTypes.h: Typedefs, Enums, Hilfsfunktionen für die State Machine (Kein ADT)
// - k2k_StateMachine.h: Verwaltung der States und ihrer State Handler (ADT ist agnostisch - unbekannt wieviele States/Events es gibt)
// - k2k_StateHandlerRstr.h: State Handler für Ristretto (ADT)
// - k2k_StateHandlerEspr.h: State Handler für Espresso (ADT)
// - k2k_StateHandlerMlkC.h: State Handler für Milchkaffee (ADT)
// - k2k_StateHandlerRdy1.h: State Handler für Ready 1 (ADT)
// - k2k_StateHandlerRdy2.h: State Handler für Ready 2 (ADT)
// - k2k_StateHandlerCln.h: State Handler für Reinigung (ADT)
// - k2k_StateHandlerOff.h: State Handler für "Off" (ADT)
// - k2k_AudioPlayer.h: Spielt Audio Dateien ab (Kein ADT)
// - k2k_Signal.h: Abstraktion von Signalen (ADT)
// 
// Ein ADT implementiert mindestens eine "Create" und eine "Release" Funktion bei welchen das Kürzel 
// und der Name des ADTs vorangestellt werden, z.B. "k2k_StateMachine_Create" und 
// "k2k_StateMachine_Release". Ausserdem beinhaltet sie Datentyp­spezifische Implementierungen. 
// 
// \subsubsection GUI Ausgaben
// Jeder State Handler bedient die für diesen Zustand relevanten GUI Elemente direkt über das 
// k2k_NanoX Modul.
// 
// \subsubsection Rdy12 Die Zustände "Ready 1" und "Ready 2"
// Nach dem Einschalten der Kaffeemaschine mit S1 Orchid, wechselt sie aus dem Zustand "Off" in den 
// Zustand "Ready 1". Bei der Vorwahl für zwei Getränke mit S5 Orchid, geht sie in den Zustand
// "Ready 2" über. Die Zustände "Ready 1" und "Ready 2" warten auf die Wahl eines Getränks. Nach der 
// Zubereitung eines Getränks geht die Maschine zurück zu "Ready 1" und prüft zunächst, ob eine 
// Reinigung erforderlich ist. Falls ja, wechselt sie automatisch in den Zustand "Clean". Nach Ablauf des 
// Reinigungsprogramms, befindet sich die Maschine wiederum in "Ready 1". 
// 
// \subsubsection RstEspMlk Die Zustände "Ristretto", "Espresso" und "Milchkaffee"
// In den Zuständen der jeweiligen Getränketypen wird eine spezifische Audiodatei abgespielt, die 
// stellvertretend für die Kaffeezubereitung steht. Das GUI zeigt während der Dateiwiedergabe eine 
// Animation. Anschliessend wird ein Kernel­Signal abgesetzt um den Wechsel zu "Ready 1" zu 
// initiieren. Wurden vorab zwei Getränke ausgewählt, wird der Audioclip zweimal abgespielt. Das Signal 
// nach Beenden des ersten Clips bewirkt in diesem Fall das Abspielen des zweiten Clips. Im Anchluss an 
// das zweite Signal wird zurück zu "Ready 1" gewechselt. 
// 
// \subsubsection Cln Der Zustand "Clean"
// Bei der Reinigung wird ebenfalls ein Audioclip abgespielt und eine zufällige Animation der LEDs 
// durchgeführt. Es wird wiederum auf das Ende des Clips gewartet, um mit Hilfe eines Signals zurück zu 
// "Ready 1" zu springen.  
// 
// \subsubsection Off Der Zustand "Off"
// In diesem Zustand ist die Maschine ausgeschaltet und reagiert lediglich auf den Schalter S1 Orchid. 
// Mit S8 Orchid kann die Applikation terminiert werden.
// 
// \subsubsection StateMachine Die State Machine
// Die State Handler registrieren sich und ihre State Handler Funktionen bei der State Machine. Die State 
// Machine verwaltet den vorherigen und den aktuellen Zustand. Nach einem eingehenden neuen Signal 
// (Event), leitet sie das Signal an die State Handler Funktion des aktuellen Zustands weiter, die das 
// Signal auswerten. 
// Die State Machine hält Funktionen um den persistenten Kaffeezähler zu erhöhen oder die gemachte 
// Anzahl an Getränken zu liefern.
// 
// \subsubsection Clips Die Audioclips
// Die abgespielten Audiodateien stehen in Stellvertretung für die mechanischen Aktivitäten der 
// Kaffeemaschine. 
// Zu hören sind die Magnetfelder des Orchidboards, die über einen Telefonadapter – ein 
// induktionsbasiertes Mikrofon – aufgenommen worden sind. 1 
// Das Mikrofon hört die sich wechselnden Magnetfelder des Orchid­Boards ab, während folgende C­
// Programme auf dem Board ausgeführt werden:
// - start: Auszug aus dem Bootprozess von einem Flash­Speicher 
// - ready1: Auszug aus dem Bootprozess von einem Flash­Speicher
// - ready2: Auszug aus dem Bootprozess via NFS
// - espresso: Animation der "k2 kaffee" GUI Elemente in normaler Geschwindigkeit
// - ristretto: Animation der "k2 kaffee" GUI Elemente in schneller Geschwindigkeit
// - milkcoffee: Betrieb der "k2 kaffee" Applikation
// - cleaning: Formatierung des Flash­Speichers mit mk2fs
// - exit: Bewegung des Mikrofons über das Board
//
// Die Aktivität der ­Programme wird sonifiziert.
// Die Animationen der GUI Elemente können über den beigelegten "trunk/emr_controller" 
// (electromagnetic radiation controller) bedient werden.
//
// \subsection INBETRIEBNAHME
//
//
// \subsubsection Driver Treiber coffee2dev installieren und laden
// Bei den abgegebenen Dateien befindet sich eine für Carme kompilierte Version des Treibers
// coffee2dev.ko im Verzeichnis "coffee2dev/coffee2dev".
// Um diese in das Root­Filesystem "/carme/rootfs" zu installieren, bitte eine Kommandozeile öffnen, 
// in das Verzeichnis "coffee2dev/coffee2dev" wechseln und "sudo cp coffee2dev.ko /carme/rootfs/usr/local/bin/"
// aufrufen. Dann auf dem Board den Treiber mit den folgenden Befehlen laden (Major gegebenenfalls anpassen!!):
//
// - cd /usr/local/bin
// - insmod coffee2dev.ko
// - cat /proc/devices | grep coffee2dev
// - mknod /dev/c2display c 250 1
// - mknod /dev/c2buttons c 250 2
// - mknod /dev/c2coffgrn c 250 6
// - mknod /dev/c2milkpmp c 250 13
//  
// Der Treiber implementiert 17 Devices welche aber nicht alle von der Applikation k2kaffee benutzt werden.
//
// \subsubsection App k2kaffee installieren und starten
//
// Bei den abgegebenen Dateien befindet sich eine für Carme kompilierte Version des Binaries 
// "k2kaffee" im Verzeichnis "coffee2dev/k2kaffee". 
// Um diese in das Root­Filesystem "/carme/rootfs" zu installieren, bitte eine Kommandozeile öffnen, in 
// das Verzeichnis "coffee2dev/k2kaffee" wechseln und dort "make install" aufrufen. 
// Es werden folgende Dateien installiert:
// - /carme/rootfs/usr/local/bin/k2kaffee
// - /carme/rootfs/usr/local/share/k2k_audio/audio_clip_clean.wav
// - /carme/rootfs/usr/local/share/k2k_audio/audio_clip_espr.wav
// - /carme/rootfs/usr/local/share/k2k_audio/audio_clip_exit.wav
// - /carme/rootfs/usr/local/share/k2k_audio/audio_clip_loaded.wav
// - /carme/rootfs/usr/local/share/k2k_audio/audio_clip_mlkc.wav
// - /carme/rootfs/usr/local/share/k2k_audio/audio_clip_rdy1.wav
// - /carme/rootfs/usr/local/share/k2k_audio/audio_clip_rdy2.wav
// - /carme/rootfs/usr/local/share/k2k_audio/audio_clip_rstr.wav
// - /carme/rootfs/usr/local/share/k2k_audio/k2k_asound.state
//
// Die Audioausgabe des Carme Boards ist in der Regel auf "mute" gesetzt. Ein "unmute" und das Setzen 
// der Lautstärke auf 100% erfolgt über den alsamixer:
//
// - alsactl ­f /usr/local/share/k2k_audio/k2k_asound.state restore
//
// Danach entweder Speaker oder Kopfhörer (Vorsicht: LAUT!!) beim Ausgang "HEAD" des Carme 
// Boards anschliessen. 
// Die Applikation starten mit:
// 
// - /usr/local/bin/k2kaffee
// 
*/

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
#include <semaphore.h>
#include <pthread.h>

#include "k2k_GlobalDefines.h"
#include "k2k_GlobalTypes.h"
#include "k2k_StateMachineTypes.h"
#include "k2k_IO.h"
#include "k2k_NanoX.h"
#include "k2k_StateMachine.h"
#include "k2k_AudioPlayer.h"
#include "k2k_StateHandlerOff.h"
#include "k2k_StateHandlerRdy1.h"
#include "k2k_StateHandlerRdy2.h"
#include "k2k_StateHandlerRstr.h"
#include "k2k_StateHandlerEspr.h"
#include "k2k_StateHandlerMlkC.h"
#include "k2k_StateHandlerCln.h"


#define MAX_LOOPS		17
#define	LED_PATTERN		0x55
#define ONE_SECOND		1000000			/* Delay value for one second in us */
#define QUARTER_SECOND	250000
#define TENTH_SECOND	100000
#define HUNDRETH_SECOND	10000
#define TINY_PAUSE		2*HUNDRETH_SECOND
#define MAIN_LOOP_SLEEP	TINY_PAUSE

/*! error codes returned by main - bitfield */
#define k2k_MAIN_ERR_SUCCESS				0x00000000
#define k2k_MAIN_ERR_CREATE_STATE_OFF		0x00000001
#define k2k_MAIN_ERR_CREATE_STATE_RDY1		0x00000002
#define k2k_MAIN_ERR_CREATE_STATE_RDY2		0x00000004
#define k2k_MAIN_ERR_CREATE_STATE_RSTR		0x00000008
#define k2k_MAIN_ERR_CREATE_STATE_ESPR		0x00000010
#define k2k_MAIN_ERR_CREATE_STATE_MLKC		0x00000020
#define k2k_MAIN_ERR_CREATE_STATE_CLN		0x00000040
#define k2k_MAIN_ERR_CREATE_IO				0x00000080
#define k2k_MAIN_ERR_CREATE_NANOX			0x00000100
#define k2k_MAIN_ERR_CREATE_STATE_MACHINE	0x00000001

/*! semaphore name for timer thread */
#define k2k_TIMER_SEMAPHORE_NAME			"/k2k_timer_semaphore"

/* -- datatype definitions ------------------------------------------*/

/*!
// timerThreadData is used to pass parameters to the timer thread 
// function - access to the data items is synchronized with a semaphore
// \brief contains parameters for the timer thread
*/		
typedef struct timerThreadData 
{	
	k2k_IO *IO;
	k2k_StateMachine *stateMachine;
	int volatile exitFlag;
} timerThreadData;

/* -- function declarations -----------------------------------------*/

void static *timer_thread_function(void *customData);
void static HandleEvent(k2k_StateMachine *stateMachine, k2k_IO *IO, k2k_Event event);

/*!
//
// This the timer thread function to generate timer events
// 
// \param stateMachine is the pointer to the statemachine ADT
// \param IO is the pointer to the IO ADT
// \param event describes the event that happened
//
// \return exit code
//
*/
void static *timer_thread_function(void *customData)
{
	timerThreadData *thdata = (timerThreadData *)customData;
	if (thdata != NULL)
	{		
		k2k_IO *IO = thdata->IO;
		k2k_StateMachine *stateMachine = thdata->stateMachine;
		sem_t *sem_timer = sem_open(k2k_TIMER_SEMAPHORE_NAME, O_RDWR);
		if (sem_timer == SEM_FAILED)
		{
			perror("error opening sem_timer");
		}
		else
		{
			int exitFlag = 0;
			while (exitFlag == 0)
			{								
				/*! acquire semaphore */
				if (sem_wait(sem_timer) < 0)
				{
					perror("error getting semaphore sem_timer");
				}
				else
				{					
					GR_EVENT grEvent;
					/*! flush Nano X buffer, check for Nano-X events */
					if (GrPeekEvent(&grEvent) != 0)
					{
						GrGetNextEvent(&grEvent);
					}
					/*! launch timer event */
					HandleEvent(stateMachine, IO, EVENT_TIMER);					
					/*! update exit flag */
					exitFlag = thdata->exitFlag;					
					/*! release semaphore */
					if (sem_post(sem_timer) < 0) 
					{
						perror("Error release semaphore sem_timer");
					}					
				}
				/*! sleep a few miliseconds */ 
				usleep(MAIN_LOOP_SLEEP);
			}
			sem_close(sem_timer);
		}
	}
	pthread_exit(NULL);    
}

/*!
//
// This a helper function to handle events and set LEDs according to the state
// 
// \param stateMachine is the pointer to the statemachine ADT
// \param IO is the pointer to the IO ADT
// \param event describes the event that happened
//
// \return exit code
//
*/
void static HandleEvent(k2k_StateMachine *stateMachine, k2k_IO *IO, k2k_Event event)
{
	/*! handle the event, set LEDs */
	k2k_State previousState = stateMachine->currentState;
	k2k_State currentState = k2k_StateMachine_ProcessEvent(stateMachine, event);
	if (currentState != previousState)
	{				
		printf("Got event %s ==> ", k2k_StateMachineTypes_GetEventText(event));	
		printf("Previous state was %s, current state is %s\n", k2k_StateMachineTypes_GetStateText(previousState), k2k_StateMachineTypes_GetStateText(currentState));
		k2k_IO_SetLeds(IO, currentState);	
	}
}
	
/*!
//
// This is the main function
// 
// \param argc  count of arguments
// \param argv	array of arguments
//
// \return exit code
//
*/
int main(int argc, char** argv)
{
	int retCode = k2k_MAIN_ERR_SUCCESS;	
	/*! create IO ADT to do memory mapping */
	k2k_IO *IO = k2k_IO_Create();	
	if (IO == NULL)
	{
		printf("ERROR: Creation of k2k_IO failed, NULL pointer returned\n");		
		retCode = k2k_MAIN_ERR_CREATE_IO;
	}
	else if (IO->err != k2k_IO_ERR_SUCCESS)
	{
		printf("ERROR: Initialization of k2k_IO failed, err is %x\n", IO->err);
		retCode = IO->err;
	}			
	else
	{
		k2k_NanoX *nanoX = k2k_NanoX_Create();
		if (nanoX == NULL)
		{
			printf("ERROR: Creation of k2k_NanoX failed, NULL pointer returned\n");		
			retCode = k2k_MAIN_ERR_CREATE_NANOX;
		}
		else if (nanoX->err != k2k_NANOX_ERR_SUCCESS)
		{
			printf("ERROR: Initialization of k2k_NanoX failed, err is %x\n", nanoX->err);
			retCode = nanoX->err;
		}
		else
		{	
			/*! do some light show */
			printf("Initializing...\n");
			k2k_IO_LightShow(IO, 1, 1, TINY_PAUSE);
			k2k_IO_LightShow(IO, 0, 1, TINY_PAUSE);
			k2k_IO_LightShow(IO, 1, 1, TINY_PAUSE);
			
			/*! create state machine */
			k2k_StateMachine *stateMachine = k2k_StateMachine_Create(nanoX);
			
			/*! create state handlers */
			k2k_StateHandlerOff *stateHandlerOff = k2k_StateHandlerOff_Create(stateMachine);
			k2k_StateHandlerRdy1 *stateHandlerRdy1 = k2k_StateHandlerRdy1_Create(stateMachine);		
			k2k_StateHandlerRdy2 *stateHandlerRdy2 = k2k_StateHandlerRdy2_Create(stateMachine);
			k2k_StateHandlerRstr *stateHandlerRstr = k2k_StateHandlerRstr_Create(stateMachine);
			k2k_StateHandlerEspr *stateHandlerEspr = k2k_StateHandlerEspr_Create(stateMachine);
			k2k_StateHandlerMlkC *stateHandlerMlkC = k2k_StateHandlerMlkC_Create(stateMachine);				
			k2k_StateHandlerCln *stateHandlerCln = k2k_StateHandlerCln_Create(stateMachine, IO);				
			
			/*! check for initialization errors */
			if (stateMachine == NULL)
			{
				printf("ERROR: Creation of stateMachine failed, NULL pointer returned\n");
				retCode += k2k_MAIN_ERR_CREATE_STATE_MACHINE;
			}
			else if (stateMachine->err != k2k_STATEHANDLER_ERR_SUCCESS)
			{
				printf("ERROR: Creation of stateMachine failed, error code is 0x%08X\n", stateMachine->err);
				retCode += k2k_MAIN_ERR_CREATE_STATE_MACHINE;
			}
			if (stateHandlerOff == NULL)
			{
				printf("ERROR: Creation of stateHandlerOff failed, NULL pointer returned\n");
				retCode += k2k_MAIN_ERR_CREATE_STATE_OFF;
			}
			if (stateHandlerRdy1 == NULL)
			{
				printf("ERROR: Creation of stateHandlerRdy1 failed, NULL pointer returned\n");
				retCode += k2k_MAIN_ERR_CREATE_STATE_RDY1;
			}
			if (stateHandlerRdy2 == NULL)
			{
				printf("ERROR: Creation of stateHandlerRdy2 failed, NULL pointer returned\n");
				retCode += k2k_MAIN_ERR_CREATE_STATE_RDY2;
			}
			if (stateHandlerRstr == NULL)
			{
				printf("ERROR: Creation of stateHandlerRstr failed, NULL pointer returned\n");
				retCode += k2k_MAIN_ERR_CREATE_STATE_RSTR;
			}
			else if (stateHandlerRstr->err != k2k_STATEHANDLER_ERR_SUCCESS)
			{
				printf("ERROR: Creation of stateHandlerRstr failed, error code is 0x%08X\n", stateHandlerRstr->err);
				retCode += k2k_MAIN_ERR_CREATE_STATE_RSTR;
			}
			if (stateHandlerEspr == NULL)
			{
				printf("ERROR: Creation of stateHandlerEspr failed, NULL pointer returned\n");
				retCode += k2k_MAIN_ERR_CREATE_STATE_ESPR;
			}
			else if (stateHandlerEspr->err != k2k_STATEHANDLER_ERR_SUCCESS)
			{
				printf("ERROR: Creation of stateHandlerEspr failed, error code is 0x%08X\n", stateHandlerEspr->err);
				retCode += k2k_MAIN_ERR_CREATE_STATE_ESPR;
			}
			if (stateHandlerMlkC == NULL)
			{
				printf("ERROR: Creation of stateHandlerMlkC failed, NULL pointer returned\n");
				retCode += k2k_MAIN_ERR_CREATE_STATE_MLKC;
			}	
			else if (stateHandlerMlkC->err != k2k_STATEHANDLER_ERR_SUCCESS)
			{
				printf("ERROR: Creation of stateHandlerMlkC failed, error code is 0x%08X\n", stateHandlerMlkC->err);
				retCode += k2k_MAIN_ERR_CREATE_STATE_MLKC;
			}	
			if (stateHandlerCln == NULL)
			{
				printf("ERROR: Creation of stateHandlerCln failed, NULL pointer returned\n");
				retCode += k2k_MAIN_ERR_CREATE_STATE_CLN;
			}	
			else if (stateHandlerCln->err != k2k_STATEHANDLER_ERR_SUCCESS)
			{
				printf("ERROR: Creation of stateHandlerCln failed, error code is 0x%08X\n", stateHandlerCln->err);
				retCode += k2k_MAIN_ERR_CREATE_STATE_CLN;
			}			
			
			if (retCode == k2k_MAIN_ERR_SUCCESS)
			{			
				/*! create timer semaphore */
				sem_unlink(k2k_TIMER_SEMAPHORE_NAME);
				sem_t *sem_timer = sem_open(k2k_TIMER_SEMAPHORE_NAME, O_CREAT, S_IRUSR | S_IWUSR, 1);
				if (sem_timer == SEM_FAILED)
				{
					printf("ERROR: Creation of timer semaphore failed\n");
				}
				else
				{
					sem_close(sem_timer);
					/*! launch pthread for timer events */					
					pthread_t timerThread;
					timerThreadData thdata;
					thdata.IO = IO;					
					thdata.stateMachine = stateMachine;					
					thdata.exitFlag = 0;
					if (pthread_create(&timerThread, NULL, timer_thread_function, (void *)&thdata) != 0)
					{
						printf("ERROR: Creation of pthread failed\n");
					}
					else
					{
						/*! open timer semaphore */
						sem_timer = sem_open(k2k_TIMER_SEMAPHORE_NAME, O_RDWR);
						if (sem_timer == SEM_FAILED)
						{
							perror("error opening sem_timer");
						}
						else
						{							
							/*! ready for operation */
							printf("Ready for input, exit application with S7 when machine is off!\n");		
							
							k2k_Event event;
							for (event = k2k_IO_GetEvent(IO); event != EVENT_S7; event = k2k_IO_GetEvent(IO))
							{						
								printf("main event loop got event %s\n", k2k_StateMachineTypes_GetEventText(event));
								/*! only call helper function if a real event happened */
								if (event != EVENT_MAX)
								{
									/*! acquire timer semaphore */
									if (sem_wait(sem_timer) < 0)
									{
										perror("error getting semaphore sem_timer");
									}
									else
									{											
										/*! handle event */
										HandleEvent(stateMachine, IO, event);		
										/*! release timer semaphore */								
										if (sem_post(sem_timer) < 0) 
										{
											perror("Error release semaphore sem_timer");
										}
									}
								}								
							}	
							/*! acquire timer semaphore, signal exit */
							if (sem_wait(sem_timer) < 0)
							{
								perror("error getting semaphore sem_timer");
							}
							else
							{
								thdata.exitFlag = 1;								
							}
							/*! release timer semaphore, signal exit */
							if (sem_post(sem_timer) < 0) 
							{
								perror("Error release semaphore sem_timer");
							}
							sem_close(sem_timer);
						}				
						/*! -- join timer thread */								
						pthread_join(timerThread, NULL);
						/*! -- delete timer semaphore */						
						sem_unlink(k2k_TIMER_SEMAPHORE_NAME);
					}					
				}
				
				/*! play exit clip */
				k2k_AudioPlayBlocked(CLIP_EXIT);
			}
			
			/*! reset LED */
			k2k_IO_WriteLed(IO, 0);
			
			/*! release handler ADT "objects" */
			k2k_StateHandlerMlkC_Release(stateHandlerMlkC);
			k2k_StateHandlerEspr_Release(stateHandlerEspr);
			k2k_StateHandlerRstr_Release(stateHandlerRstr);
			k2k_StateHandlerRdy2_Release(stateHandlerRdy2);
			k2k_StateHandlerRdy1_Release(stateHandlerRdy1);
			k2k_StateHandlerOff_Release(stateHandlerOff);
			k2k_StateHandlerCln_Release(stateHandlerCln);
			k2k_StateMachine_Release(stateMachine);	
		}
		/*! release nanoX "object" */
		if (nanoX != NULL)
		{
			k2k_NanoX_Release(nanoX);
		}
	}
	/*! release IO "object" */
	if (IO != NULL)
	{
		k2k_IO_Release(IO);
	}
	
	printf("exiting, return code is %x\n", retCode);	
	return retCode;	 
}
